﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Tunneler.Tools {
    public class CollisionDetector : ICollisionDetector {
        World world = World.Instance;

        Matrix transformA;
        Matrix transformB;

        public void HandleCollidingObjects() {
            for (int i = 0; i < world.GameObjects.Count; ++i) {
                for (int j = i + 1; j < world.GameObjects.Count; ++j) {
                    IGameObject a = world.GameObjects[i];
                    IGameObject b = world.GameObjects[j];

                    if (AreColliding(a, b)) {
                        a.TouchedBy(b);
                        b.TouchedBy(a);
                    }
                }
            }
        }

        public void HandleTerrainCollisions() {
            List<IGameObject> terCollidingObjects = new List<IGameObject>();
            foreach (IGameObject gameObject in world.GameObjects) {
                if (AreColliding(gameObject, world.Terrain)) {
                    terCollidingObjects.Add(gameObject);
                }
            }

            foreach (IGameObject gameObject in terCollidingObjects) {
                gameObject.TouchedBy(world.Terrain);
                world.Terrain.TouchedBy(gameObject);
            }
        }

        public bool IntersectBounds(IGameObject a, IGameObject b) {
            transformA = CalculateTransform(a);
            transformB = CalculateTransform(b);
            Rectangle recA = CalculateBoundingRectangle(
                     new Rectangle(0, 0, a.Texture.Width, a.Texture.Height),
                     transformA);
            Rectangle recB = CalculateBoundingRectangle(
                     new Rectangle(0, 0, b.Texture.Width, b.Texture.Height),
                     transformB);
            return recA.Intersects(recB);
        }

        public bool AreColliding(IGameObject a, IGameObject b) {
            if (IntersectBounds(a, b)) {
                return IntersectPixels(transformA, a.Texture.Width, a.Texture.Height, a.TextureData,
                    transformB, b.Texture.Width, b.Texture.Height, b.TextureData);
            } else {
                return false;
            }
        }

        private bool AreColliding(IGameObject gameObject, Terrain terrain) {
            Matrix goTransform = CalculateTransform(gameObject);

            Tuple<List<Texture2D>, List<Color[]>, List<Vector2>> tuple = terrain.GetBoundingTexturesAndData(gameObject);
            List<Texture2D> textures = tuple.Item1;
            List<Color[]> texturesData = tuple.Item2;
            List<Vector2> texturesPositions = tuple.Item3;

            bool areColliding = false;
            for (int i = 0; i < textures.Count; ++i) {
                Matrix terTransform = Matrix.CreateTranslation(new Vector3(texturesPositions[i], 0f));

                areColliding = IntersectPixels(goTransform, gameObject.Texture.Width, gameObject.Texture.Height, gameObject.TextureData,
                terTransform, textures[i].Width, textures[i].Height, texturesData[i]);

                if (areColliding) {
                    break;
                }
            }

            return areColliding;
        }

        private static Matrix CalculateTransform(IGameObject gameObject) {
            Matrix transform =
                Matrix.CreateTranslation(new Vector3(-gameObject.Origin, 0.0f)) *
                Matrix.CreateScale(gameObject.TextureScale) *
            Matrix.CreateRotationZ(gameObject.Rotation) *
            Matrix.CreateTranslation(new Vector3(gameObject.Position, 0.0f));

            return transform;
        }

        // Most likely original source (found it on stack overflow myself)
        #region Collision Detection Code By Jim Perry and Allen Sherrod, book Essential XNA Game Studio 2.0 Programming
        private static bool IntersectPixels(
                            Matrix transformA, int widthA, int heightA, Color[] dataA,
                            Matrix transformB, int widthB, int heightB, Color[] dataB) {
            // Calculate a matrix which transforms from A's local space into
            // world space and then into B's local space
            Matrix transformAToB = transformA * Matrix.Invert(transformB);

            // When a point moves in A's local space, it moves in B's local space with a
            // fixed direction and distance proportional to the movement in A.
            // This algorithm steps through A one pixel at a time along A's X and Y axes
            // Calculate the analogous steps in B:
            Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAToB);
            Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAToB);

            // Calculate the top left corner of A in B's local space
            // This variable will be reused to keep track of the start of each row
            Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAToB);

            // For each row of pixels in A
            for (int yA = 0; yA < heightA; yA++) {
                // Start at the beginning of the row
                Vector2 posInB = yPosInB;

                // For each pixel in this row
                for (int xA = 0; xA < widthA; xA++) {
                    // Round to the nearest pixel
                    int xB = (int)Math.Round(posInB.X);
                    int yB = (int)Math.Round(posInB.Y);

                    // If the pixel lies within the bounds of B
                    if (0 <= xB && xB < widthB &&
                        0 <= yB && yB < heightB) {
                        // Get the colors of the overlapping pixels
                        Color colorA = dataA[xA + yA * widthA];
                        Color colorB = dataB[xB + yB * widthB];

                        // If both pixels are not completely transparent,
                        if (colorA.A != 0 && colorB.A != 0) {
                            // then an intersection has been found
                            return true;
                        }
                    }

                    // Move to the next pixel in the row
                    posInB += stepX;
                }

                // Move to the next row
                yPosInB += stepY;
            }

            // No intersection found
            return false;
        }

        private static Rectangle CalculateBoundingRectangle(Rectangle rectangle,
                                                          Matrix transform) {
            // Get all four corners in local space
            Vector2 leftTop = new Vector2(rectangle.Left, rectangle.Top);
            Vector2 rightTop = new Vector2(rectangle.Right, rectangle.Top);
            Vector2 leftBottom = new Vector2(rectangle.Left, rectangle.Bottom);
            Vector2 rightBottom = new Vector2(rectangle.Right, rectangle.Bottom);

            // Transform all four corners into work space
            Vector2.Transform(ref leftTop, ref transform, out leftTop);
            Vector2.Transform(ref rightTop, ref transform, out rightTop);
            Vector2.Transform(ref leftBottom, ref transform, out leftBottom);
            Vector2.Transform(ref rightBottom, ref transform, out rightBottom);

            // Find the minimum and maximum extents of the rectangle in world space
            Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop),
                                      Vector2.Min(leftBottom, rightBottom));
            Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop),
                                      Vector2.Max(leftBottom, rightBottom));

            // Return that as a rectangle
            return new Rectangle((int)min.X, (int)min.Y,
                                 (int)(max.X - min.X), (int)(max.Y - min.Y));
        }

        #endregion

    }
}
